package com.ztp.lib.server;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import org.apache.ftpserver.ConnectionConfigFactory;
import org.apache.ftpserver.DataConnectionConfigurationFactory;
import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.FtpServerFactory;
import org.apache.ftpserver.ftplet.DefaultFtplet;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.FtpRequest;
import org.apache.ftpserver.ftplet.FtpSession;
import org.apache.ftpserver.ftplet.Ftplet;
import org.apache.ftpserver.ftplet.FtpletResult;
import org.apache.ftpserver.impl.FtpIoSession;
import org.apache.ftpserver.listener.Listener;
import org.apache.ftpserver.listener.ListenerFactory;
import org.apache.ftpserver.ssl.SslConfigurationFactory;
import org.apache.ftpserver.usermanager.ClearTextPasswordEncryptor;
import org.apache.ftpserver.usermanager.DbUserManagerFactory;
import org.apache.ftpserver.usermanager.PasswordEncryptor;
import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory;
import org.apache.ftpserver.usermanager.UserManagerFactory;
import org.apache.log4j.PropertyConfigurator;

import static java.lang.System.out;


public class ServerHelper {
	public static final String SETTINGS_FILENAME 			= Env.getDataDir() + "setting.cfg";
	public static final String USER_PROPERTIES_FILENAME 	= Env.getDataDir() + "users.properties";
	public static final String LOG4J_PROPERTIES_FILENAME 	= Env.getDataDir() + "log4j.properties";
	
	public static final boolean USE_DB_UMF 					= false;
	
	public ConnectionConfigFactory ccf 						= null;
	public UserManagerFactory umf 							= null;
	public DataConnectionConfigurationFactory dccf 			= null;
	public ListenerFactory lf 								= null;
	public FtpServerFactory serverFactory 					= null;
	public SslConfigurationFactory sslcf 					= null;
	public FtpServer server 								= null;
	public FtpSession oneClientSession 						= null;
	DownloadWatcher downloadWatcher 						= null;
	UploadWatcher uploadWatcher 							= null;
	Listener listener 										= null;
	FtpIoSession ioSession 									= null; 
	public PasswordEncryptor pw = new ClearTextPasswordEncryptor();
	
	
	
	
	public ServerHelper() throws IOException, Exception{
		loadLogProperties();
		loadUserMenagerFactory();
		recreateFacories(true);
	}
	
	void loadLogProperties() throws IOException, Exception{
		if(Env.getOS().equals(Env.OS_ANDROID)) return;
		File logPrptsFile = new File( LOG4J_PROPERTIES_FILENAME );
		boolean isNew = logPrptsFile.createNewFile();
		PrintWriter pw = null;
		if(isNew){
			try {
				pw = new PrintWriter(new BufferedOutputStream(new FileOutputStream(logPrptsFile)), false);
				pw.println("log4j.rootLogger=DEBUG, C");
				pw.println("log4j.appender.C=org.apache.log4j.ConsoleAppender");
				pw.println("log4j.appender.C.layout=org.apache.log4j.PatternLayout");
				pw.println("log4j.appender.C.layout.ConversionPattern=[%5p] %d [%X{userName}] [%X{remoteIp}] %m%n");
				pw.flush();
				pw.close();
			} 
			catch (Exception e) {
				pw.close();
				logPrptsFile.delete();
				throw e;
			}
		}
		
		
		/*Properties p = new Properties();
		p.load(new FileInputStream(logPrptsFile));*/
		PropertyConfigurator.configure( LOG4J_PROPERTIES_FILENAME );
	}
	
	void loadUserMenagerFactory() throws IOException, Exception{
		if(USE_DB_UMF){
			
			
			
			DbUserManagerFactory dumf = new DbUserManagerFactory();
			dumf.setPasswordEncryptor(pw);
			dumf.setSqlUserAuthenticate("SELECT userpassword FROM users WHERE userid = '{userid}'");
			dumf.setSqlUserDelete("DELETE FROM users WHERE userid='{userid}'");
			dumf.setSqlUserInsert("INSERT INTO users (" +
					"userid, userpassword, homedirectory" +
					", enableflag, writepermission, idletime, uploadrate, downloadrate) VALUES(" +
					"'{userid}', '{userpassword}', '{homedirectory}',"+
					"'{enableflag}', '{writepermission}', {idletime}, {uploadrate}, {downloadrate})");
			dumf.setSqlUserSelect(
					"SELECT userid, userpassword, homedirectory,"+
					"enableflag, writepermission, idletime, uploadrate, downloadrate "+
			        "FROM users "+
			        "WHERE userid = '{userid}'"
					);
			dumf.setSqlUserSelectAll("SELECT userid FROM users ORDER BY userid");
			dumf.setSqlUserUpdate(
				"UPDATE users SET "
	            +"userpassword='{userpassword}',"
	            +"homedirectory='{homedirectory}',"
	            +"enableflag={enableflag},"
	            +"writepermission={writepermission},"
	            +"idletime={idletime},"
	            +"uploadrate={uploadrate},"
	            +"downloadrate={downloadrate}"
				+"WHERE userid='{userid}'"
			);
			return;
		}
		
		File usersProperties = new File( USER_PROPERTIES_FILENAME );
		boolean isNew = usersProperties.createNewFile();
		if(isNew){
			PrintWriter pw = null;
			try {
				pw = new PrintWriter(new BufferedOutputStream(new FileOutputStream(usersProperties)), false);
				pw.println("#for admin user");
				pw.println("ftpserver.user.admin.userpassword=admin");
				pw.println("ftpserver.user.admin.homedirectory=" + Env.getHome());
				pw.println("ftpserver.user.admin.enableflag=true");
				pw.println("ftpserver.user.admin.writepermission=true");
				pw.println("ftpserver.user.admin.maxloginnumber=0");
				pw.println("ftpserver.user.admin.maxloginperip=0");
				pw.println("ftpserver.user.admin.idletime=0");
				pw.println("ftpserver.user.admin.uploadrate=0");
				pw.println("ftpserver.user.admin.downloadrate=0");
				pw.flush();
				pw.close();
			} 
			catch(Exception e){
				pw.close();
				usersProperties.delete();
				throw e;
			}
		}
		
		PropertiesUserManagerFactory pumf = new PropertiesUserManagerFactory();
		pumf.setFile(usersProperties);
		pumf.setPasswordEncryptor( pw);
		umf = pumf;
		
	}
	
	void recreateFacories(boolean loadFromDataSource){
		ccf = new ConnectionConfigFactory();
		dccf = new DataConnectionConfigurationFactory();
		lf = new ListenerFactory();
		sslcf = new SslConfigurationFactory();
		
		if(loadFromDataSource){
			try {
				ServerHelper.loadSettings(ccf, dccf, lf);
			} catch (IOException e1) {
				out.println("cannot load settings from file");
				e1.printStackTrace();
			}
		}
		composeServer();
	}
	
	public void composeServer(){
		lf.setDataConnectionConfiguration(dccf.createDataConnectionConfiguration());
		serverFactory = new FtpServerFactory();
		serverFactory.setConnectionConfig(ccf.createConnectionConfig());
		serverFactory.setUserManager(umf.createUserManager());
		listener = lf.createListener();
		serverFactory.addListener("default", listener);
		
		LinkedHashMap<String, Ftplet> ftpletMap = new LinkedHashMap<String, Ftplet>();
		ftpletMap.put("custom", new CustomFtplet());
		
		serverFactory.setFtplets(ftpletMap );
		server = serverFactory.createServer();
	}
	
	public void startServer() throws FtpException{
		composeServer();
		server.start();
	}
	
	public void stopAll(){
		stopDownloadWatcher();
		stopUploadWatcher();
		server.stop();
	}
	
	class Watcher extends Thread{
		boolean run = true;
	}
	class DownloadWatcher extends Watcher{
		@Override
		public void run() {
			
			if(ioSession == null){
				ioSession = listener.getActiveSessions().iterator().next();
				out.println("---ioSessions " + ioSession.getUser().getName() + " " + ioSession.getSessionId().toString());
			}
			else {
				FtpIoSession newSession = listener.getActiveSessions().iterator().next();
				if(ioSession == newSession){
					out.println("---nothing new");
				}
				else{
					ioSession = newSession;
					out.println("---new session set");
					out.println("---ioSessions " + ioSession.getUser().getName() + " " + ioSession.getSessionId().toString());
				}
			}
			
			long lastBytes = ioSession.getWrittenBytes();
			long lastTime = System.currentTimeMillis();
			
			long newBytes;
			long newTime;
			
			long dBytes, dTime;
			
			try {
				while(run){
					Thread.sleep(500);
					
					//out.println("FileOffset" + ioSession.getFileOffset());
					//out.println("ReadBytes" + ioSession.getReadBytes());
					
					newBytes = ioSession.getWrittenBytes();
					newTime = System.currentTimeMillis();
					
					dBytes = newBytes - lastBytes;
					dTime = newTime - lastTime;
					
					out.println("speed " +  (double) dBytes / dTime  );
					
					out.println("WrittenBytes" + newBytes);
					
					lastBytes = newBytes;
					lastTime = newTime;
					
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	class UploadWatcher extends Watcher{
		@Override
		public void run() {
			if(ioSession == null){
				ioSession = listener.getActiveSessions().iterator().next();
				out.println("---ioSessions " + ioSession.getUser().getName() + " " + ioSession.getSessionId().toString());
			}
			else {
				FtpIoSession newSession = listener.getActiveSessions().iterator().next();
				if(ioSession == newSession){
					out.println("---nothing new");
				}
				else{
					ioSession = newSession;
					out.println("---new session set");
					out.println("---ioSessions " + ioSession.getUser().getName() + " " + ioSession.getSessionId().toString());
				}
			}
			long lastBytes = ioSession.getReadBytes();
			long lastTime = System.currentTimeMillis();
			
			long newBytes;
			long newTime;
			
			long dBytes, dTime;
			
			try {
				while(run){
					Thread.sleep(500);
	
					newBytes = ioSession.getReadBytes();
					newTime = System.currentTimeMillis();
					
					dBytes = newBytes - lastBytes;
					dTime = newTime - lastTime;
					
					out.println("speed " +  (double) dBytes / dTime  );
					out.println("WrittenBytes" + newBytes);
					
					lastBytes = newBytes;
					lastTime = newTime;
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void startDownloadWatcher(){
		if(downloadWatcher == null){
			downloadWatcher = new DownloadWatcher();
			downloadWatcher.start();
			return;
		}
	}
	public void stopDownloadWatcher(){
		while(downloadWatcher != null){
			downloadWatcher.run = false;
			try {
				downloadWatcher.join(500);
				downloadWatcher = null;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void startUploadWatcher(){
		if(uploadWatcher == null){
			uploadWatcher = new UploadWatcher();
			uploadWatcher.start();
			return;
		}
	}
	public void stopUploadWatcher(){
		while(uploadWatcher != null){
			uploadWatcher.run = false;
			try {
				uploadWatcher.join(500);
				uploadWatcher = null;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public class CustomFtplet extends DefaultFtplet {
		
		@Override
		public FtpletResult onDownloadEnd(FtpSession session, FtpRequest request)throws FtpException, IOException {
			System.out.println("onDownloadEnd");
			stopDownloadWatcher();
			//printSession(session);
			//printRequest(request);
			return super.onDownloadEnd(session, request);
		}
		
		@Override
		public FtpletResult onDownloadStart(FtpSession session, FtpRequest request)throws FtpException, IOException {
			System.out.println("onDownloadStart");
			startDownloadWatcher();
			//printSession(session);
			//printRequest(request);
			return super.onDownloadStart(session, request);
		}
		
		@Override
		public FtpletResult onUploadStart(FtpSession session, FtpRequest request) throws FtpException, IOException {
			System.out.println("onUploadStart");
			startUploadWatcher();
			return super.onUploadStart(session, request);
		}
		
		@Override
		public FtpletResult onUploadEnd(FtpSession session, FtpRequest request) throws FtpException, IOException {
			System.out.println("onUploadEnd");
			stopUploadWatcher();
			return super.onUploadEnd(session, request);
		}
	}
	
	public static void loadSettings(ConnectionConfigFactory ccf
			, DataConnectionConfigurationFactory dccf
			, ListenerFactory lf) throws IOException{
		boolean firstUse = false;
		BufferedReader br = null;
		try{
		
			File settingsFile = new File( SETTINGS_FILENAME);
			firstUse = settingsFile.createNewFile();
			if(firstUse){
				if(Env.getOS() == Env.OS_ANDROID || Env.getOS() == Env.OS_LINUXPC){
					lf.setPort(2222);
				}
				
				saveSettings(ccf, dccf, lf);
				return;
			}
			
			br = new BufferedReader(new InputStreamReader(new FileInputStream(settingsFile)));
			ArrayList<String[]> splLines = new ArrayList<String[]>();
			
			while(br.ready()){
				String line = br.readLine();
				if(line.matches("^\\s*#.*") || line.matches("\\s+")) continue;
				splLines.add(line.split("\\s+"));
			}
			
			for(String [] splLine : splLines){
				if(splLine[1].equals("null")) splLine[1] = null;
				
				if(splLine[0].equals("AnonymousLoginEnabled")){
					ccf.setAnonymousLoginEnabled(Boolean.parseBoolean(splLine[1]));
				}
				else if(splLine[0].equals("MaxAnonymousLogins")){
					ccf.setMaxAnonymousLogins(Integer.valueOf(splLine[1]));
				}
				else if(splLine[0].equals("MaxLogins")){
					ccf.setMaxLogins(Integer.valueOf(splLine[1]));
				}
				else if(splLine[0].equals("MaxLoginFailures")){
					ccf.setMaxLoginFailures(Integer.valueOf(splLine[1]));
				}
				else if(splLine[0].equals("LoginFailureDelay")){
					ccf.setLoginFailureDelay(Integer.valueOf(splLine[1]));
				}
				else if(splLine[0].equals("MaxThreads")){
					ccf.setMaxThreads(Integer.valueOf(splLine[1]));
				}
				//----------------------------------------------------------------------------------
				else if(splLine[0].equals("ActiveEnabled")){
					dccf.setActiveEnabled(Boolean.parseBoolean(splLine[1]));
				}
				else if(splLine[0].equals("ActiveIpCheck")){
					dccf.setActiveIpCheck(Boolean.parseBoolean(splLine[1]));
				}
				else if(splLine[0].equals("ActiveLocalAddress")){
					dccf.setActiveLocalAddress(splLine[1]);
				}
				else if(splLine[0].equals("ActiveLocalPort")){
					dccf.setActiveLocalPort(Integer.valueOf(splLine[1]));
				}
				else if(splLine[0].equals("PassiveAddress")){
					dccf.setPassiveAddress(splLine[1]);
				}
				else if(splLine[0].equals("PassiveExternalAddress")){
					dccf.setPassiveExternalAddress(splLine[1]);
				}
				else if(splLine[0].equals("PassivePorts")){
					dccf.setPassivePorts(splLine[1]);
				}
				else if(splLine[0].equals("IdleTime")){
					dccf.setIdleTime(Integer.valueOf(splLine[1]));
				}
				else if(splLine[0].equals("ImplicitSsl")){
					dccf.setImplicitSsl(Boolean.parseBoolean(splLine[1]));
				}
				//----------------------------------------------------------------------------------
				else if(splLine[0].equals("IdleTimeout")){
					lf.setIdleTimeout(Integer.valueOf(splLine[1]));
				}
				else if(splLine[0].equals("LFImplicitSsl")){
					lf.setImplicitSsl(Boolean.parseBoolean(splLine[1]));
				}
				else if(splLine[0].equals("ServerAddress")){
					lf.setServerAddress(splLine[1]);
				}
				else if(splLine[0].equals("Port")){
					lf.setPort(Integer.valueOf(splLine[1]));
				}
			}
		}
		finally{
			if(br != null){
				try{
					br.close();
				}
				catch(IOException ioe){
					ioe.printStackTrace();
				}
			}
		}
	}
	
	public static void saveSettings(ConnectionConfigFactory ccf
			, DataConnectionConfigurationFactory dccf
			, ListenerFactory lf) throws IOException {
		
		PrintWriter pw = null;
		try {
			pw = new PrintWriter(new FileOutputStream(new File( SETTINGS_FILENAME), false),false);
			pw.println("# ConnectionConfigFactory");
			pw.println("AnonymousLoginEnabled\t" 		+ ccf.isAnonymousLoginEnabled());
			pw.println("MaxAnonymousLogins\t" 			+ ccf.getMaxAnonymousLogins());
			pw.println("MaxLogins\t" 					+ ccf.getMaxLogins());
			pw.println("MaxLoginFailures\t" 			+ ccf.getMaxLoginFailures());
			pw.println("LoginFailureDelay\t" 			+ ccf.getLoginFailureDelay());
			pw.println("MaxThreads\t" 					+ ccf.getMaxThreads());
			
			pw.println("# DataConnectionConfigurationFactory");
			pw.println("ActiveEnabled\t\t" 				+ dccf.isActiveEnabled());
			pw.println("ActiveIpCheck\t\t" 				+ dccf.isActiveIpCheck());
			pw.println("ActiveLocalAddress\t\t" 		+ dccf.getActiveLocalAddress());
			pw.println("ActiveLocalPort\t\t" 			+ dccf.getActiveLocalPort());
			pw.println("PassiveAddress\t\t" 			+ dccf.getPassiveAddress());
			pw.println("PassiveExternalAddress\t\t" 	+ dccf.getPassiveExternalAddress());
			pw.println("PassivePorts\t\t" 				+ dccf.getPassivePorts());
			pw.println("IdleTime\t\t" 					+ dccf.getIdleTime());
			pw.println("ImplicitSsl\t\t" 				+ dccf.isImplicitSsl());
			
			pw.println("# ListenerFactory");
			pw.println("IdleTimeout\t\t" 				+ lf.getIdleTimeout());
			pw.println("LFImplicitSsl\t\t" 				+ lf.isImplicitSsl());
			pw.println("ServerAddress\t\t" 				+ lf.getServerAddress());
			pw.println("Port\t\t" 						+ lf.getPort());
			
			pw.flush();
			pw.close();
		} 
		finally{
			if(pw != null){
				pw.close();
			}
		}
	}
	
}


/*
CREATE TABLE IF NOT EXISTS users (
userid VARCHAR(64)  PRIMARY KEY
, userpassword VARCHAR(64)
, homedirectory VARCHAR(128)
, enableflag BOOLEAN DEFAULT TRUE
, writepermission BOOLEAN DEFAULT FALSE
, idletime INT DEFAULT 0
, upoloadrate INT DEFAULT 0
, downloadrate INT DEFAULT 0
);

 * */
